package main.threading;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import main.listeners.ListenerContainer;

import settings.Config;

/**
 * @author Wasserleiche
 */
public class ThreadPool {
	
	protected ThreadPoolExecutor executor;
	
	protected ListenerContainer controllers;
	protected List<Runnable> postRunnables, addedPostRunnables;
	protected int maxThreads;
	protected Collection<Class<?>> exceptions;
	protected boolean iteratingPostRunnables, iteratingControllers, changed;
	
	protected List<ThreadPoolContainer> containers;
	
	public ThreadPool() {
		exceptions = new HashSet<Class<?>>();
		
		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
		maxThreads = getMaxThreads();
		executor = new ThreadPoolExecutor(maxThreads, maxThreads, 20000, TimeUnit.MILLISECONDS, queue);
		
		postRunnables = new ArrayList<Runnable>();
		addedPostRunnables = new ArrayList<Runnable>();
		
		controllers = new ListenerContainer(ThreadPoolController.class);
	}
	
	protected int getMaxThreads() { return Runtime.getRuntime().availableProcessors() - 1; }
	
	protected void addJob(Runnable r) { executor.submit(r); }
	
	public void addPostRunnable(Runnable r) {
		if(!iteratingPostRunnables) postRunnables.add(r);
		else addedPostRunnables.add(r);
	}
	
	public void registerController(ThreadPoolController c) {
		controllers.addListener(c);
		changed = true;
	}
	
	public void unregisterController(ThreadPoolController c) {
		controllers.removeListener(c);
		changed = true;
	}
		
	public void startExecution(float t) {
		if(controllers.size() == 0) return;
		
		final Float time = Float.valueOf(t);
		if(maxThreads == 0 || !Config.get().getEnableThreading()) {
			controllers.callMethod(ThreadPoolController.UPDATE, time);
			runPostRunnables();
		} else {
			if(changed) remakeContainers();

			for(int i = 1; i < containers.size(); i++) {
				ThreadPoolContainer c = containers.get(i);
				c.setTime(time);
				addJob(c);
			}
			
			try {
				ThreadPoolContainer c = containers.get(0);
				assert(c != null);
				c.setTime(time);
				c.run();
				
				while(!executor.getQueue().isEmpty() || executor.getActiveCount() != 0) {}
				
				runPostRunnables();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	protected void remakeContainers() {
		containers = new ArrayList<ThreadPoolContainer>();
		
		int size = controllers.size();
		int parts = maxThreads + 1;
		int partSize = size / parts;
		
		for(int i = 0; i < parts; i++) {
			int start = partSize * i;
			int end = partSize * (i + 1);
			ThreadPoolContainer c = new ThreadPoolContainer(controllers, start, end);
			containers.add(c);
		}
		
		changed = false;
	}
	
	protected void runPostRunnables() {
		iteratingPostRunnables = true;
		for(Runnable r : postRunnables) {
			r.run();
		}
		postRunnables.clear();
		iteratingPostRunnables = false;
		
		if(!addedPostRunnables.isEmpty()) {
			for(int i = 0; i < addedPostRunnables.size(); i++) {
				postRunnables.add(addedPostRunnables.get(i));
			}
			addedPostRunnables.clear();
			runPostRunnables();
		}
	}
	
	public void cleanup() {
		controllers.cleanUp();
		executor.purge();
		executor.shutdownNow();
	}
}